/*
 * This file is part of aion-unique <aion-unique.smfnew.com>.
 *
 *  aion-emu is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  aion-emu is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with aion-emu.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionemu.gameserver.model.gameobjects.stats;

import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javolution.util.FastMap;
import javolution.util.FastMap.Entry;

import org.apache.log4j.Logger;

import com.aionemu.gameserver.model.SkillElement;
import com.aionemu.gameserver.model.gameobjects.Creature;
import com.aionemu.gameserver.model.gameobjects.Item;
import com.aionemu.gameserver.model.gameobjects.NpcWhichNotGatherable;
import com.aionemu.gameserver.model.gameobjects.stats.modifiers.StatModifier;
import com.aionemu.gameserver.model.items.ManaStone;
import com.aionemu.gameserver.skillengine.model.IStatEffect;

/**
 * @author xavier
 * 
 */
public abstract class CreatureGameStats<T extends NpcWhichNotGatherable>
{
	protected static final Logger							log					= Logger.getLogger(CreatureGameStats.class);

	private static final int								ATTACK_MAX_COUNTER	= Integer.MAX_VALUE;
	
	protected FastMap<StatEnum, TreeSet<OwnedStatModifier>>	stats;
	private int												attackCounter		= 0;
	protected T												owner				= null;
	protected final ReentrantReadWriteLock					lock				= new ReentrantReadWriteLock();

	/**
	 * 
	 * @param owner
	 */
	protected CreatureGameStats(T owner)
	{
		this.owner = owner;
		this.stats = new FastMap<StatEnum, TreeSet<OwnedStatModifier>>();
	}

	/**
	 * @return the atcount
	 */
	public int getAttackCounter()
	{
		return attackCounter;
	}

	/**
	 * @param atcount
	 *            the atcount to set
	 */
	protected void setAttackCounter(int attackCounter)
	{
		if (attackCounter <= 0)
		{
			this.attackCounter = 1;
		}
		else
		{
			this.attackCounter = attackCounter;
		}
	}

	public void increaseAttackCounter()
	{
		if (attackCounter == ATTACK_MAX_COUNTER)
		{
			this.attackCounter = 1;
		}
		else
		{
			this.attackCounter++;
		}
	}

	public Stat getStat(StatEnum statEnum, float base)
	{
		Stat stat = new Stat(base, (Creature) owner);
		getStat(statEnum, stat);
		return stat;
	}

	public Stat getSummonStatBoost(StatEnum statEnum, Stat stat)
	{

		lock.readLock().lock();
		try
		{
			TreeSet<OwnedStatModifier> modifiers = stats.get(statEnum);
			if (modifiers == null || modifiers.isEmpty())
				return stat;
			for (OwnedStatModifier modifier : modifiers)
			{
				if (modifier.getOwner() instanceof Item || modifier.getOwner() instanceof ManaStone)
					modifier.getStatModifier().apply(stat);
			}
		}
		finally
		{
			lock.readLock().unlock();
		}
		return stat;
	}

	public Stat getStat(StatEnum statEnum, Stat stat)
	{
		lock.readLock().lock();
		try
		{
			TreeSet<OwnedStatModifier> modifiers = stats.get(statEnum);
			if (modifiers == null || modifiers.isEmpty())
				return stat;
			for (OwnedStatModifier modifier : modifiers)
			{
				if (modifier.getStatModifier().verify(stat))	
					modifier.getStatModifier().apply(stat);
			}
		}
		finally
		{
			lock.readLock().unlock();
		}
		return stat;
	}

	/**
	 * 
	 * @param id
	 * @param modifiers
	 */
	public boolean addModifiers(IStatEffect effectOwner, List<StatModifier> modifiers)
	{
		if (modifiers == null)
			return false;
		boolean needStatUpdate = false;
		lock.writeLock().lock();
		try
		{
			for (StatModifier modifier : modifiers)
			{
				OwnedStatModifier ownedModifier = new OwnedStatModifier(effectOwner, modifier);
				if (!stats.containsKey(modifier.getStat()))
					stats.put(modifier.getStat(), new TreeSet<OwnedStatModifier>());
				TreeSet<OwnedStatModifier> mods = stats.get(modifier.getStat());
				if (mods.contains(ownedModifier))
				{
					log.warn("Effect " + effectOwner + " already active"+modifier.toString());
				}
				needStatUpdate |= modifier.getStat().isupdateStat();
				mods.add(ownedModifier);
			}
		}
		finally
		{
			lock.writeLock().unlock();
		}
		return needStatUpdate;
	}

	/**
	 * 
	 * @param id
	 */
	public boolean endEffect(IStatEffect id)
	{
		boolean needStatUpdate = false;
		lock.writeLock().lock();
		try
		{
			for (Entry<StatEnum, TreeSet<OwnedStatModifier>> e = stats.head(), end = stats.tail(); (e = e.getNext()) != end;)
			{
				TreeSet<OwnedStatModifier> value = e.getValue();
				for (Iterator<OwnedStatModifier> iter = value.iterator(); iter.hasNext();)
				{
					OwnedStatModifier ownedMod = iter.next();
					if (ownedMod.getOwner() != null && ownedMod.getOwner().equals(id))
					{
						iter.remove();
						needStatUpdate |= ownedMod.getStatModifier().getStat().isupdateStat();
					}
				}
			}
		}
		finally
		{
			lock.writeLock().unlock();
		}
		return needStatUpdate;
	}

	/**
	 * @return True if the StatEffectId is already added
	 */
	public boolean effectAlreadyAdded(IStatEffect id)
	{
		for (TreeSet<OwnedStatModifier> mods : stats.values())
		{
			for (OwnedStatModifier ownedMod : mods)
				if (ownedMod.getOwner()!= null && ownedMod.getOwner().equals(id))
					return true;
		}
		return false;
	}

	/**
	 * 
	 * @param element
	 * @return
	 */
	public float getMagicalDefenseFor(SkillElement element)
	{
		switch (element)
		{
			case EARTH:
				return getStat(StatEnum.EARTH_RESISTANCE, 0).getCurrent();
			case FIRE:
				return getStat(StatEnum.FIRE_RESISTANCE, 0).getCurrent();
			case WATER:
				return getStat(StatEnum.WATER_RESISTANCE, 0).getCurrent();
			case WIND:
				return getStat(StatEnum.WIND_RESISTANCE, 0).getCurrent();
			default:
				return 0;
		}
	}
	
	public abstract Stat getMaxHP();
	
	public abstract Stat getMaxMP();
	
	public abstract Stat getAttackSpeed();
	
	public abstract Stat getMovementSpeed();
	
	public abstract Stat getAttackRange();
	
	public abstract Stat getPDef();
	
	public abstract Stat getMagicalResist();
	
	public abstract Stat getPower();
	public abstract Stat getHealth();
	public abstract Stat getAccuracy();
	public abstract Stat getAgility();
	public abstract Stat getKnowledge();
	public abstract Stat getWill();
	
	public abstract Stat getMainHandPAttack();
	
	public abstract Stat getMainHandPCritical();
	
	public abstract Stat getMainHandPAccuracy();
	
	public abstract Stat getMagicalBoost();
	
	public abstract Stat getMagicalAccuracy();
	
	public abstract Stat getMagicalCritical();
	
	public abstract Stat getEvasion();
	public abstract Stat getParry();
	public abstract Stat getBlock();

	/**
	 * @return Returns the stats.
	 */
	public FastMap<StatEnum, TreeSet<OwnedStatModifier>> getStats()
	{
		return stats;
	}
}
